import Vue from 'vue'
import axios from 'axios'
import { trim, isString, isPlainObject, isArray } from '@engineer/utils'
import store from '@/store'
import router from '@/router'
import requestWhiteList from './request-white-list'

const vueInstance = new Vue()

/**
 * 获取trim的配置项
 * @param {Object} options 配置项
 * @returns {Object} options配置项
 */
const getTrimConfig = (function() {
  const DEFAULT_CONFIG = {
    all: true, // 全部过滤
    exclude: ['loginPassword'] // 不过滤的key
  }
  return (options = {}) => {
    const { all = true, exclude = [] } = options
    const mergeExclude = [].concat(DEFAULT_CONFIG.exclude, exclude)
    return {
      all: all || DEFAULT_CONFIG.all,
      exclude: mergeExclude
    }
  }
}())

/**
 * 处理对象中的字符串类型值的前后空格
 * @param {Object} data 对象
 * @param {Object} [options={}] 配置项
 * @returns {Object} 处理后的对象
 */
function disposeValuesTrim(data, options = {}) {
  const { all = true, exclude = [] } = options
  // 是否进行前后空格过滤处理
  if (!all) {
    return data
  }

  // 非空处理和普通数据类型处理
  if (!data || (typeof data !== 'object')) {
    return data
  }

  // 复杂数据类型处理
  if (isArray(data)) {
    data.forEach(item => {
      item = disposeValuesTrim(item, options)
    })
  }

  if (isPlainObject(data)) {
    for (const key in data) {
      // 不做处理的字段
      if (exclude.includes(key)) {
        continue
      }
      // 复杂数据类型处理
      if (typeof data[key] === 'object') {
        data[key] = disposeValuesTrim(data[key], options)
        continue
      }
      // 字符串做前后空格过滤
      // 其他普通数据类型 ，不做处理
      if (isString(data[key])) {
        data[key] = trim(data[key])
      }
    }
  }

  return data
}

/**
 * 网络超时
 */
const TIMEOUT = 20000

/**
 * 退出登录
 * @param {Object} errData 错误数据
 * @returns {void}
 */
const redLogOutReload = errData => {
  vueInstance.$message.error(errData.msg)
  store.dispatch('user/logOut')
}

/**
 * 403错误处理
 * @param {Object} errData 错误信息
 * @returns {void}
 */
const localLessTokenSign = errData => {
  const { code, msg } = errData
  if (code === 403527) {
    vueInstance.$message.error('本地时间小于生成token签名时间')
    return
  }

  if (code === 403601) {
    vueInstance.$message.error('暂无权限调用接口，请联系管理员!')
    return
  }

  if (code === 403604) {
    vueInstance.$confirm('当前帐号没有权限访问，请刷新页面', '提示', {
      type: 'warning',
      showClose: false,
      showCancelButton: false,
      confirmButtonText: '刷新'
    }).then(() => {
      window.location.reload()
    }).catch(() => {})
    return
  }

  vueInstance.$message.error(msg)
}

/**
 * 请求频率限制
 * @param {Object} errData 错误信息
 * @returns {void}
 */
const operateFrequent = () => {
  vueInstance.$message.error('您的操作太频繁了~')
}

/**
 * 对应的状态处理方法
 * @param {string} status 状态码
 * @param {Object} errData 错误信息
 * @returns {void}
 */
const createErrorHandler = (() => {
  const errorCodeFns = {
    401: redLogOutReload,
    403: localLessTokenSign,
    429: operateFrequent
  }
  let messageInstance = null
  return (status, errData) => {
    if (errorCodeFns[status]) {
      errorCodeFns[status](errData)
    } else {
      const { msg } = errData
      if (msg || status === 500) {
        messageInstance && messageInstance.close()
        messageInstance = vueInstance.$message.error(msg || '服务器出错')
      }
    }
  }
})()

/**
 * 校验url字符串是否符合指定的url校验规则
 * @param {string} urlRule url校验规则字符串
 * @param {string} url 校验的url字符串
 * @returns {boolean} 是否匹配
 */
const verifyRequestUrl = (urlRule, url) => new RegExp('^' + urlRule.replace('\d+', '-?\\d+') + '$').test(url)

/**
 * 当前请求是否在白名单中，属于白名单请求
 * @param {Object} config axios的config配置
 * @returns {boolean} 是否是白名单请求
 */
const isWhiteRequest = config => {
  const { url, method } = config
  // 白名单的校验
  return requestWhiteList.some(item => {
    const { url: whiteUrl, method: whiteMethod } = item
    return method === whiteMethod && verifyRequestUrl(whiteUrl, url)
  })
}

/**
 * 查找匹配的路由权限数组
 * @param {Object} param0 参数
 * @param {string} param0.name 路由名
 * @param {string} param0.path 路由路径
 * @param {array} param0.roleTree 权限
 * @returns {array} roles
 */
function findApiRoles({ name, path, roleTree = [] }) {
  let res = []
  for (let i = 0; i < roleTree.length; i++) {
    const parent = roleTree[i]
    const { name: parentName, path: parentPath, children = [] } = parent
    if (res.length) {
      break
    }
    if (parentName === name || parentPath === path) {
      res = children
    } else {
      if (children.length) {
        const temp = findApiRoles({ name, path, roleTree: children })
        if (temp.length) {
          res = temp
        }
      }
    }
  }

  return res
}

/**
 * 将按钮权限底下的api权限
 * @param {array} apiRoles roles
 * @returns {array} apiPermission
 */
function getApiPermission(apiRoles = []) {
  const res = []
  apiRoles.forEach(itme => {
    const { roles } = itme
    res.push(...roles)
  })
  return res
}

/**
 * 查找请求的nodeid
 * @param {Object} config axios的config配置
 * @returns {number} nodeid
 */
const findRequestNodeId = config => {
  const { url, method } = config
  let nodeid = 0

  // 不属于白名单的请求
  if (!isWhiteRequest(config)) {
    const { currentRoute = {}} = router
    const { name, path } = currentRoute
    // console.log(name, path)
    const { getters = {}} = store
    const { roleTree } = getters
    const apiRoles = findApiRoles({ name, path, roleTree })
    const apiPermission = getApiPermission(apiRoles)
    // 校验当前的请求是否在接口权限列表中
    const item = apiPermission.find(item => verifyRequestUrl(item.url, url) && item.method.toLowerCase() === method)
    if (item) {
      nodeid = item.nodeid
    }
  } else {
    // 白名单请求不添加nodeid
    nodeid = -1
  }

  return nodeid
}

/**
 * 处理请求的权限控制操作
 * @param {Object} config axios的config配置
 * @returns {boolean} 是否有权限
 */
const disposeRequestPermission = config => {
  const nodeid = findRequestNodeId(config)
  if (nodeid !== 0) {
    if (nodeid > 0) {
      config.headers['Nodeid'] = nodeid
    }
    return true
  }

  return false
}

const server = axios.create({
  timeout: TIMEOUT,
  baseURL: process.env.VUE_APP_BASE_API,
  headers: {
    Accept: 'application/vnd.hamster_action.v1+json',
    Type: 13
  }
})

// 声明一个数组用于存储每个ajax请求的取消函数和ajax标识
const pendingList = []

/**
 * 删除取消请求的队列
 * @param {AxiosConfig} config axios的配置
 * @returns {void}
 */
const removePending = config => {
  pendingList.forEach((pending, index) => {
    if (pending.key === `${config.url}&${config.method}`) { // 当前请求在数组中存在时执行函数体
      pending.f('cancel request') // 执行取消操作
      pendingList.splice(index, 1) // 把这条记录从数组中移除
    }
  })
}

server.interceptors.request.use(config => {
  const { cancel = false, trim = true, exclude = [], method } = config

  // 判断当前的请求是否需要加入到队列中，做取消上一次请求的操作
  if (cancel) {
    removePending(config) // 在一个请求发送前执行一下取消操作
    config.cancelToken = new axios.CancelToken(c => {
      // 请求的标识：请求地址&请求方式拼接的字符串&qs.stringify(携带的params参数)
      pendingList.push({
        key: config.url + '&' + config.method,
        f: c
      })
    })
  }

  // token
  if (store.getters.token) {
    config.headers['x-authorization'] = store.getters.token
  }

  // 通过判断环境变量，判断是否关闭接口请求拦截
  if (process.env.VUE_APP_REQUEST_INTERCEPT !== 'disabled') {
    const flag = disposeRequestPermission(config)

    if (!flag) {
      console.log(`method: ${config.method}，${config.url} 接口请求没有权限`)
      return Promise.reject(`method: ${config.method}，${config.url} 接口请求没有权限`)
    }
  }

  // 字符串数据前后空格处理
  if (['PUT', 'POST', 'PATCH'].includes(method.toUpperCase()) && trim) {
    const { data } = config
    config.data = disposeValuesTrim(data, getTrimConfig({ exclude }))
  }

  return config
}, err => {
  return Promise.reject(err)
})

server.interceptors.response.use(res => {
  // 在一个请求响应后再执行一下取消操作，把已经完成的请求从pendingList中移除
  removePending(res.config)
  return res.data
}, error => {
  // 判断错误是否属于取消请求的
  if (axios.isCancel(error)) {
    return Promise.reject(error)
  }

  if (typeof error === 'string') {
    return Promise.reject(new Error(error))
  }

  if (error.response) {
    const { response: { status, data: errData }} = error
    createErrorHandler(status, errData)
    return Promise.reject(errData)
  } else {
    if (error.message === `timeout of ${TIMEOUT}ms exceeded`) {
      vueInstance.$message.error('网络不佳~')
    } else {
      vueInstance.$message.error(error.message)
    }
    return Promise.reject(new Error(error.message))
  }
})

export default server
